Text mining
References
The reference book is: https://www.tidytextmining.com
And the package is:
if(!require(tidytext)){install.packages("tidytext", repos = "https://cloud.r-project.org/")}
library(tidytext)
Data retrieval
Now, let’s move forward to simple text analysis. First, we need to prepare the data! (as usual)
tokens <- tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text) %>% # Keeps only id and text of the tweet
unnest_tokens(word, text) # Creates tokens!
tokens
Let’s have a look at word frequencies.
tokens %>%
count(word, sort = TRUE)
This is polluted by small words. Let’s filter that (FIRST METHOD).
tokens %>% mutate(length = nchar(word))
Data frequencies
Now let’s omit the small words (smaller than 5 characters).
NOTE: all the thresholds below depend on the sample!
tokens %>%
mutate(length = nchar(word)) %>%
filter(length > 4) %>% # Keep words with length larger than 4
count(word, sort = TRUE) %>% # Count words
head(18) %>% # Keep only top 12 words
ggplot(aes(x = reorder(word,n), y = n)) + geom_col() + coord_flip() +
xlab("Words")

A better way to proceed is to remove “stop words” like “a”, “I”, “of”, etc (SECOND METHOD). Also, it would make sense to remove the search item and “https”.
data("stop_words")
tidy_tokens <- tokens %>%
anti_join(stop_words) # Remove unrelevant terms
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(20) %>% # Keep only top 15 words
ggplot(aes(x = reorder(word,n), y = n)) + geom_col() + coord_flip() +
xlab("Words")

Problem: strange characters remain. We are going to remove them by converting the text to ASCII format and omit NA data.
tidy_tokens <- tokens %>%
anti_join(stop_words) %>% # Remove unrelevant
mutate(word = iconv(word, from = "UTF-8", to = "ASCII")) %>% # Put in latin format
na.omit() %>% # Remove missing
filter(nchar(word) > 3, # Remove small words
!(word %in% c("https", "t.co", search_term)) # search_term defined above
)
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(15) %>% # Keep only top words
ggplot(aes(x = reorder(word,n), y = n)) + geom_col() + coord_flip() +
xlab("Words")

Perfect!
Sentiment
This section is inspired from: https://www.tidytextmining.com/sentiment.html
Sometimes, you may be asked in the process if you really want to download data (lexicons).
Just say yes in the console (type the correct answer: if not, you will be blocked/struck).
First, we need to load some sentiment lexicon. AFINN is one such sentiment database.
if(!require(textdata)){install.packages("textdata", repos = "https://cloud.r-project.org/")}
Loading required package: textdata
library(tidytext)
library(textdata)
afinn <- get_sentiments("afinn")
afinn
To create a nice visualization, we need to extract the time of the tweets.
tokens_time <- tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(word, text) # Creates tokens!
tokens_time
We then use inner_join() to merge the two sets. This function removes the cases when a match does not occur.
library(lubridate)
Attaching package: ‘lubridate’
The following objects are masked from ‘package:base’:
date, intersect, setdiff, union
sentiment <- tokens_time %>%
inner_join(afinn) %>%
mutate(day = day(created_at),
hour = hour(created_at) / 24,
minute = minute(created_at) / 60 / 24,
time = day + hour + minute)
Joining, by = "word"
sentiment
We then compute the average sentiment, minute-by-minute.
Of course, average sentiment can be misleading. Indeed, if a text contains the terms “I’m not happy”, then only “happy” will be tagged, which is the opposite of the intended meaning.
sentiment %>%
group_by(time, day, hour, minute) %>%
summarise(avg_sentiment = mean(value)) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = avg_sentiment)) + geom_col()
`summarise()` regrouping output by 'time', 'day', 'hour' (override with `.groups` argument)

There are 24 bars per day, but the y-axis is not optimal…
What about emotions? The NRC lexicon categorizes emotions. Below, we order emotions. The most important impact is the dichotomy between positive & negative emotions.
nrc <- get_sentiments("nrc")
nrc <- nrc %>%
mutate(sentiment = as.factor(sentiment),
sentiment = recode_factor(sentiment,
joy = "joy",
trust = "trust",
surprise = "surprise",
anticipation = "anticipation",
positive = "positive",
negative = "negative",
sadness = "sadness",
anger = "anger",
fear = "fear",
digust = "disgust",
.ordered = T))
We then create the merged dataset.
emotions <- tokens_time %>%
inner_join(nrc) %>% # Merge data with sentiment
mutate(day = day(created_at),
hour = hour(created_at)/24,
minute = minute(created_at)/24/60,
time = day+hour+minute) # Create day column
Joining, by = "word"
emotions # Show the result
The merging has reduced the size of the dataset, but there still remains enough to pursue the study.
Finally, we move to the pivot-table that counts emotions for each day.
g <- emotions %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col() +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time")
`summarise()` regrouping output by 'time', 'sentiment', 'day', 'hour' (override with `.groups` argument)
ggplotly(g)
This can also be shown in percentage format.
g <- emotions %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time")
`summarise()` regrouping output by 'time', 'sentiment', 'day', 'hour' (override with `.groups` argument)
ggplotly(g)
Going further would probably involve n-grams, see https://www.tidytextmining.com/ngrams.html
Advanced sentiment
The problem with the preceding methods is that they don’t take into account valence shifters (i.e., negators, amplifiers (intensifiers), de-amplifiers (downtoners), and adversative conjunctions). If a tweet says not happy, counting the word happy is not a good idea! The package sentimentr is built to circumvent these issues: have a look at https://github.com/trinker/sentimentr
(see also: https://www.sentometrics.org)
if(!require(sentimentr)){install.packages(c("sentimentr", "textcat"))}
library(sentimentr)
library(textcat)
First, let’s keep only the tweets written in English!
tweets_en <- tweets %>%
mutate(language = textcat(text)) %>%
filter(language == "english") %>%
dplyr::select(created_at, text)
NOTE: the code above was used to show the function textcat: the language is already coded in the tweets via the lang column/variable. (it suffices to keep the instances for which lang == “en”)
Next, we compute advanced sentiment.
tweet_sent <- tweets_en$text %>%
get_sentences() %>% # Intermediate function
sentiment() # Sentiment!
tweet_sent
NOTE: depending on frequency issues, it is better to analyze at daily or hourly scales. If a word is very popular, then, higher frequencies are more relevant.
tweets_en %>%
rowid_to_column("element_id") # This creates a new column with row number
tweets_en %>%
rowid_to_column("element_id") %>%
left_join(tweet_sent, by = "element_id")
tweets_en %>%
rowid_to_column("element_id") %>%
left_join(tweet_sent, by = "element_id") %>%
group_by(day = day(created_at)) %>%
summarise(avg_sent = mean(sentiment)) %>%
ggplot(aes(x = as.factor(day), y = avg_sent)) + geom_col() + xlab("day")
`summarise()` ungrouping output (override with `.groups` argument)

tweets_en %>%
rowid_to_column("element_id") %>%
left_join(tweet_sent, by = "element_id") %>%
ggplot(aes(x = as.factor(day(created_at)), y = sentiment)) +
geom_jitter(size = 0.2) +
geom_boxplot(aes(color = as.factor(day(created_at))), alpha = 0.5) +
theme(legend.position = "none") + xlab("day")

LS0tCnRpdGxlOiAiVGhpcmQgcGFydHkgZGF0YSBhbmQgYmFzaWMgdGV4dCBtaW5pbmciCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIFRoZSBnZW5lcmFsIGlkZWEKCkRhdGEgdHJhbnNmZXIgaXMgaGlnaGx5IGNvbnRyb2xsZWQuIFRoZSBrZXkgbm90aW9ucyBhcmUgKiphdXRoZW50aWNhdGlvbioqIGFuZCAqKnByb3RvY29sKiouCgojIERvd25sb2FkaW5nIHR3ZWV0cyB3aXRoICpydHdlZXQqCgpUaGVyZSBhcmUgc2V2ZXJhbCBwYWNrYWdlcyB0aGF0IHJ1biBhbiBpbnRlcmZhY2Ugd2l0aCB0d2l0dGVyOiAqcnR3ZWV0KiwgKlJUd2l0dGVyQVBJKiwgKnN0cmVhbVIqIGFuZCAqdHdpdHRlUiouCQkKUmVjZW50IHBhY2thZ2VzIGFyZSBiZXR0ZXIgYmVjYXVzZSBmaXJtcyB1cGRhdGUgdGhlaXIgQVBJIHBvbGljaWVzIChhbmQgYWNjZXNzKSwgdGh1cyBvbGQgcHJvdG9jb2xzIHNvbWV0aW1lcyBkbyBub3Qgd29yayEKCiMjIEZpcnN0IHRoaW5ncyBmaXJzdAoqKkZpcnN0KiosIHRoZSBwYWNrYWdlcy4gRG93bmxvYWQuLi4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUocnR3ZWV0KSl7aW5zdGFsbC5wYWNrYWdlcygicnR3ZWV0Iil9CmBgYAoKLi4uIGFuZCBhY3RpdmF0ZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJ0d2VldCkKYGBgCgojIyBBdXRoZW50aWNhdGlvbgoKKipTZWNvbmQqKjogeW91IG5lZWQgeW91ciB0d2l0dGVyIGNyZWRlbnRpYWxzICh5b3UgbmVlZCBhIHR3aXR0ZXIgYWNjb3VudCkuCkxvZ2luIG9uIHR3aXR0ZXIgYW5kIGdvIHRvOiBodHRwczovL2RldmVsb3Blci50d2l0dGVyLmNvbSAKCiFbXSh0d2l0dGVyMS5wbmcpCgpUaGUgbmV4dCBzdGVwIGlzIGNydWNpYWw6IHdlIG5lZWQgdG8gcmV0cmlldmUgaWRlbnRpZmljYXRpb24gY3JlZGVudGlhbHMuICAgCkluIG9yZGVyIHRvIGRvIHRoYXQsIHlvdSBuZWVkIHRvIGNyZWF0ZSBhIFR3aXR0ZXIgYXBwLiBCZWxvdywgeW91IGNhbiBzZWUgbWluZS4gClRvIGNyZWF0ZSBvbmUsIHNpbXBseSBjbGljayBvbiB0aGUgIkNyZWF0ZSBhbiBhcHAiICBidXR0b24gKG9uIHRoZSByaWdodCkKCiFbXSh0d2l0dGVyMi5wbmcpCgpJZiB5b3UgY2xpY2sgb24gdGhlICJkZXRhaWxzIiBvZiBhbiBhcHAsIHlvdSBjYW4gc2VlIHRoaXM6CgohW10odHdpdHRlcjMucG5nKQoKVGhlIHNlY29uZCB0YWIgaXMgY2FsbGVkICIqKktleXMgYW5kIHRva2VucyoqIiAkXHJpZ2h0YXJyb3ckIHRoYXQncyB3aGVyZSB0aGUgaW5mbyBpcyEhIQoKIVtdKHR3aXR0ZXI0LnBuZykKCgpOb3cgd2UgYXJlIHJlYWR5IHRvIHByb2NlZWQuIFRoZSBsaW5lcyBiZWxvdyBvcGVuIHRoZSBjb25uZXhpb24gd2l0aCB0aGUgQVBJLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBldmFsID0gRkFMU0V9CmNvbnN1bWVyX2tleSA8LSAieW91cl9jb25zdW1lcl9rZXkiCmNvbnN1bWVyX3NlY3JldCA8LSAieW91X2NvbnN1bWVyX3NlY3JldCIKYWNjZXNzX3Rva2VuIDwtICJ5b3VyX2FjY2Vzc190b2tlbiIKYWNjZXNzX3NlY3JldCA8LSAieW91cl9hY2Nlc3Nfc2VjcmV0IgoKY3JlYXRlX3Rva2VuKGFwcCA9ICJ0aGVfbmFtZV9vZl95b3VyX2FwcCIsCiAgICAgICAgICAgICBjb25zdW1lcl9rZXkgPSBjb25zdW1lcl9rZXksIAogICAgICAgICAgICAgY29uc3VtZXJfc2VjcmV0ID0gY29uc3VtZXJfc2VjcmV0LCAKICAgICAgICAgICAgIGFjY2Vzc190b2tlbiA9IGFjY2Vzc190b2tlbiwgCiAgICAgICAgICAgICBhY2Nlc3Nfc2VjcmV0ID0gYWNjZXNzX3NlY3JldAogICAgICAgICAgICAgKQpgYGAKCgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CmNvbnN1bWVyX2tleSA8LSAieHFNbTEwVnd3bDFYQXgzMUNBellOaXFvaSIKY29uc3VtZXJfc2VjcmV0IDwtICJEUkxjS2pxUExiaW96MmkyVkVXbldyQVFqWVJVRkNMNHNVbk5hekN4WnVROEJ0VHFjZCIKYWNjZXNzX3Rva2VuIDwtICIzMjYxMDUyODkxLU9BcVBqRWtVUXJna01Pa1V5RXZ5V1JEVnhhNzZKRmE5ZTUyTmROVCIKYWNjZXNzX3NlY3JldCA8LSAiNGYzc2tqZm53TGF2T1pHTmh0dWV0SWxFNGdzeDhDR1hFaTJHTXdhUmZGN24wIgoKY3JlYXRlX3Rva2VuKGFwcCA9ICJCaWcgRG91ZG91IiwKICAgICAgICAgICAgIGNvbnN1bWVyX2tleSA9IGNvbnN1bWVyX2tleSwgCiAgICAgICAgICAgICBjb25zdW1lcl9zZWNyZXQgPSBjb25zdW1lcl9zZWNyZXQsIAogICAgICAgICAgICAgYWNjZXNzX3Rva2VuID0gYWNjZXNzX3Rva2VuLCAKICAgICAgICAgICAgIGFjY2Vzc19zZWNyZXQgPSBhY2Nlc3Nfc2VjcmV0CiAgICAgICAgICAgICApCmBgYAoKQXV0aGVudGljYXRpb24gaXMgYW4gaW1wb3J0YW50IHBhcnQgb2YgdGhlIHByb2Nlc3MuIEZvciBtb3JlIGluZm8gb24gdGhhdDogIAotIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nb29nbGVzaGVldHMvdmlnbmV0dGVzL21hbmFnaW5nLWF1dGgtdG9rZW5zLmh0bWwgICAKLSBodHRwczovL2h0dHIuci1saWIub3JnL3JlZmVyZW5jZS9pbmRleC5odG1sIChzZWN0aW9uIEF1dGhlbnRpY2F0aW9uKQoKIyMgRXh0cmFjdGlvbgpJZiBubyBlcnJvciBhcHBlYXJzLCB3ZSBhcmUgcmVhZHkgdG8gcXVlcnkuIERlcGVuZGluZyBvbiB0aGUgbnVtYmVyIG9mIHJlcXVlc3RlZCB0d2VldHMsIHRoaXMgY2FuIHRha2Ugc29tZSB0aW1lLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpzZWFyY2hfdGVybSA8LSAiaGFycmlzIgp0d2VldHMgPC0gc2VhcmNoX3R3ZWV0cygKICBzZWFyY2hfdGVybSwgICAgICAgICAgIyBXaGF0IHRvIHNlYXJjaCBmb3IKICBuID0gMjAwMCwgICAgICAgICAgICAgIyBOdW1iZXIgb2YgdHdlZXRzIHRvIGRvd25sb2FkCiAgaW5jbHVkZV9ydHMgPSBGQUxTRSAgICMgRXhjbHVkZSByZS10d2VldHMKKQpgYGAKRm9yIGxhcmdlIHF1ZXJpZXMsIHRoZSBwcm9ncmVzcyBiYXIgaGVscHMuICAgCk5vdGUgdGhhdCBtYW55IG9wdGlvbnMgYXJlIGF2YWlsYWJsZSwgbGlrZTogZXhjbHVkZSByZXR3ZWV0cywgbGltaXQgc2VhcmNoIHRvIHBhcnRpY3VsYXIgZ2VvZ3JhcGhpY2FsIHpvbmVzIChpbnNpZGUgcmFkaXVzZXMpLgoKIyBUZXh0IG1pbmluZwoKIyMgUmVmZXJlbmNlcwpUaGUgcmVmZXJlbmNlIGJvb2sgaXM6IGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbSAgICAgIApBbmQgdGhlIHBhY2thZ2UgaXM6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHRpZHl0ZXh0KSl7aW5zdGFsbC5wYWNrYWdlcygidGlkeXRleHQiLCByZXBvcyA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmcvIil9CmxpYnJhcnkodGlkeXRleHQpCmBgYAoKCiMjIERhdGEgcmV0cmlldmFsCgpOb3csIGxldCdzIG1vdmUgZm9yd2FyZCB0byBzaW1wbGUgdGV4dCBhbmFseXNpcy4gRmlyc3QsIHdlIG5lZWQgdG8gcHJlcGFyZSB0aGUgZGF0YSEgKGFzIHVzdWFsKQoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp0b2tlbnMgPC0gdHdlZXRzICU+JSAKICAgIG11dGF0ZShpZCA9IDE6bnJvdyh0d2VldHMpKSAlPiUgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCkgJT4lICAgICAgICAgICAgICMgS2VlcHMgb25seSBpZCBhbmQgdGV4dCBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgICAgICAgICMgQ3JlYXRlcyB0b2tlbnMhCnRva2VucwpgYGAKCkxldCdzIGhhdmUgYSBsb29rIGF0IHdvcmQgZnJlcXVlbmNpZXMuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUKICAgIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpgYGAKClRoaXMgaXMgcG9sbHV0ZWQgYnkgc21hbGwgd29yZHMuIExldCdzIGZpbHRlciB0aGF0ICgqRklSU1QgTUVUSE9EKikuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUgbXV0YXRlKGxlbmd0aCA9IG5jaGFyKHdvcmQpKQpgYGAKCgojIyBEYXRhIGZyZXF1ZW5jaWVzCk5vdyBsZXQncyBvbWl0IHRoZSBzbWFsbCB3b3JkcyAoc21hbGxlciB0aGFuIDUgY2hhcmFjdGVycykuICAgCioqTk9URSoqOiBhbGwgdGhlIHRocmVzaG9sZHMgYmVsb3cgZGVwZW5kIG9uIHRoZSBzYW1wbGUhIAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp0b2tlbnMgJT4lCiAgICBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpICU+JQogICAgZmlsdGVyKGxlbmd0aCA+IDQpICU+JSAgICAgICAgICAgICAjIEtlZXAgd29yZHMgd2l0aCBsZW5ndGggbGFyZ2VyIHRoYW4gNAogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JSAgICAgICAjIENvdW50IHdvcmRzCiAgICBoZWFkKDE4KSAlPiUgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCAxMiB3b3JkcwogICAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih3b3JkLG4pLCB5ID0gbikpICsgZ2VvbV9jb2woKSArIGNvb3JkX2ZsaXAoKSArCiAgeGxhYigiV29yZHMiKQpgYGAKCkEgYmV0dGVyIHdheSB0byBwcm9jZWVkIGlzIHRvIHJlbW92ZSAic3RvcCB3b3JkcyIgbGlrZSAiYSIsICJJIiwgIm9mIiwgZXRjICgqU0VDT05EIE1FVEhPRCopLgpBbHNvLCBpdCB3b3VsZCBtYWtlIHNlbnNlIHRvIHJlbW92ZSB0aGUgc2VhcmNoIGl0ZW0gYW5kICJodHRwcyIuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmRhdGEoInN0b3Bfd29yZHMiKQp0aWR5X3Rva2VucyA8LSB0b2tlbnMgJT4lIAogICAgYW50aV9qb2luKHN0b3Bfd29yZHMpICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSB1bnJlbGV2YW50IHRlcm1zCnRpZHlfdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JSAgICAgICAgICAgICAjIENvdW50IHdvcmRzCiAgICBoZWFkKDIwKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCAxNSB3b3JkcwogICAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih3b3JkLG4pLCB5ID0gbikpICsgZ2VvbV9jb2woKSArIGNvb3JkX2ZsaXAoKSArCiAgICAgIHhsYWIoIldvcmRzIikKYGBgCgoqKlByb2JsZW0qKjogc3RyYW5nZSBjaGFyYWN0ZXJzIHJlbWFpbi4gV2UgYXJlIGdvaW5nIHRvIHJlbW92ZSB0aGVtIGJ5IGNvbnZlcnRpbmcgdGhlIHRleHQgdG8gQVNDSUkgZm9ybWF0IGFuZCBvbWl0ICpOQSogZGF0YS4gCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRpZHlfdG9rZW5zIDwtIHRva2VucyAlPiUgCiAgICBhbnRpX2pvaW4oc3RvcF93b3JkcykgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHVucmVsZXZhbnQKICAgIG11dGF0ZSh3b3JkID0gaWNvbnYod29yZCwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJIikpICU+JSAjIFB1dCBpbiBsYXRpbiBmb3JtYXQKICAgIG5hLm9taXQoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgbWlzc2luZwogICAgZmlsdGVyKG5jaGFyKHdvcmQpID4gMywgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBzbWFsbCB3b3JkcwogICAgICAgICAgICEod29yZCAlaW4lIGMoImh0dHBzIiwgInQuY28iLCBzZWFyY2hfdGVybSkpICAjIHNlYXJjaF90ZXJtIGRlZmluZWQgYWJvdmUKICAgICkKdGlkeV90b2tlbnMgJT4lCiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICAgIyBDb3VudCB3b3JkcwogICAgaGVhZCgxNSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCB3b3JkcwogICAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih3b3JkLG4pLCB5ID0gbikpICsgZ2VvbV9jb2woKSArIGNvb3JkX2ZsaXAoKSArCiAgeGxhYigiV29yZHMiKQpgYGAKClBlcmZlY3QhCgojIyBXb3JkIGNsb3VkCgpUaGlzIGRhdGEgY2FuIGFsc28gYmUgc2hvd24gd2l0aCBhIHdvcmQgY2xvdWQuIFdlIHNpbXBseSB1c2UgdGhlICp3b3JkY2xvdWQqIHBhY2thZ2U6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93b3JkY2xvdWQvaW5kZXguaHRtbCAKClRoZSBwYWNrYWdlICp3b3JkY2xvdWQyKiBhZGRzIGEgZmV3IGZlYXR1cmVzOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvd29yZGNsb3VkMi92aWduZXR0ZXMvd29yZGNsb3VkLmh0bWwKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUod29yZGNsb3VkKSl7aW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIil9CmxpYnJhcnkod29yZGNsb3VkKQpjbG91ZF9kYXRhIDwtIHRpZHlfdG9rZW5zICU+JSBjb3VudCh3b3JkKQp3b3JkY2xvdWQod29yZHMgPSBjbG91ZF9kYXRhJHdvcmQsIAogICAgICAgICAgZnJlcSA9IGNsb3VkX2RhdGEkbiwgbWluLmZyZXEgPSAyLAogICAgICAgICAgbWF4LndvcmRzID0gMTAwLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgcm90LnBlcj0wLjM1LCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQpgYGAKCiMjIFNlbnRpbWVudAoKVGhpcyBzZWN0aW9uIGlzIGluc3BpcmVkIGZyb206IGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS9zZW50aW1lbnQuaHRtbCAgICAKU29tZXRpbWVzLCB5b3UgbWF5IGJlIGFza2VkIGluIHRoZSBwcm9jZXNzIGlmIHlvdSAqcmVhbGx5KiB3YW50IHRvIGRvd25sb2FkIGRhdGEgKGxleGljb25zKS4gIApKdXN0IHNheSB5ZXMgaW4gdGhlICoqY29uc29sZSoqICh0eXBlIHRoZSBjb3JyZWN0IGFuc3dlcjogaWYgbm90LCB5b3Ugd2lsbCBiZSBibG9ja2VkL3N0cnVjaykuCgpGaXJzdCwgd2UgbmVlZCB0byBsb2FkIHNvbWUgc2VudGltZW50IGxleGljb24uIEFGSU5OIGlzIG9uZSBzdWNoIHNlbnRpbWVudCBkYXRhYmFzZS4gCgpgYGB7cn0KaWYoIXJlcXVpcmUodGV4dGRhdGEpKXtpbnN0YWxsLnBhY2thZ2VzKCJ0ZXh0ZGF0YSIsIHJlcG9zID0gImh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZy8iKX0KbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeSh0ZXh0ZGF0YSkKYWZpbm4gPC0gZ2V0X3NlbnRpbWVudHMoImFmaW5uIikKYWZpbm4KYGBgCgpUbyBjcmVhdGUgYSBuaWNlIHZpc3VhbGl6YXRpb24sIHdlIG5lZWQgdG8gZXh0cmFjdCB0aGUgKip0aW1lKiogb2YgdGhlIHR3ZWV0cy4KCmBgYHtyfQp0b2tlbnNfdGltZSA8LSB0d2VldHMgJT4lIAogICAgbXV0YXRlKGlkID0gMTpucm93KHR3ZWV0cykpICU+JSAgICAjIFRoaXMgY3JlYXRlcyBhIHR3ZWV0IGlkCiAgICBzZWxlY3QoaWQsIHRleHQsIGNyZWF0ZWRfYXQpICU+JSAgICMgS2VlcHMgaWQsIHRleHQgYW5kIGRhdGUgb2YgdGhlIHR3ZWV0CiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICAgICAgICAgICMgQ3JlYXRlcyB0b2tlbnMhCnRva2Vuc190aW1lCmBgYAoKV2UgdGhlbiB1c2UgKippbm5lcl9qb2luKiooKSB0byBtZXJnZSB0aGUgdHdvIHNldHMuIFRoaXMgZnVuY3Rpb24gcmVtb3ZlcyB0aGUgY2FzZXMgd2hlbiBhIG1hdGNoIGRvZXMgbm90IG9jY3VyLgoKYGBge3J9CmxpYnJhcnkobHVicmlkYXRlKQpzZW50aW1lbnQgPC0gdG9rZW5zX3RpbWUgJT4lIAogIGlubmVyX2pvaW4oYWZpbm4pICU+JQogIG11dGF0ZShkYXkgPSBkYXkoY3JlYXRlZF9hdCksCiAgICAgICAgIGhvdXIgPSBob3VyKGNyZWF0ZWRfYXQpIC8gMjQsCiAgICAgICAgIG1pbnV0ZSA9IG1pbnV0ZShjcmVhdGVkX2F0KSAvIDYwIC8gMjQsCiAgICAgICAgIHRpbWUgPSBkYXkgKyBob3VyICsgbWludXRlKQpzZW50aW1lbnQKYGBgCgpXZSB0aGVuIGNvbXB1dGUgdGhlIGF2ZXJhZ2Ugc2VudGltZW50LCBtaW51dGUtYnktbWludXRlLiAgIApPZiBjb3Vyc2UsIGF2ZXJhZ2Ugc2VudGltZW50IGNhbiBiZSBtaXNsZWFkaW5nLiBJbmRlZWQsIGlmIGEgdGV4dCBjb250YWlucyB0aGUgdGVybXMgIipJJ20gbm90IGhhcHB5KiIsIHRoZW4gb25seSAiKmhhcHB5KiIgd2lsbCBiZSB0YWdnZWQsIHdoaWNoIGlzIHRoZSBvcHBvc2l0ZSBvZiB0aGUgaW50ZW5kZWQgbWVhbmluZy4KCmBgYHtyfQpzZW50aW1lbnQgJT4lCiAgZ3JvdXBfYnkodGltZSwgZGF5LCBob3VyLCBtaW51dGUpICU+JQogIHN1bW1hcmlzZShhdmdfc2VudGltZW50ID0gbWVhbih2YWx1ZSkpICU+JQogIG11dGF0ZSh0aW1lID0gbWFrZV9kYXRldGltZSh5ZWFyID0gMjAyMCwgbW9udGggPSAxMCwgZGF5ID0gZGF5LCBob3VyID0gaG91cioyNCwgbWluID0gbWludXRlKjI0KjYwKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGF2Z19zZW50aW1lbnQpKSArIGdlb21fY29sKCkKYGBgClRoZXJlIGFyZSAyNCBiYXJzIHBlciBkYXksIGJ1dCB0aGUgKnkqLWF4aXMgaXMgbm90IG9wdGltYWwuLi4gIAogCldoYXQgYWJvdXQgZW1vdGlvbnM/IFRoZSAqKk5SQyoqIGxleGljb24gY2F0ZWdvcml6ZXMgZW1vdGlvbnMuIEJlbG93LCB3ZSBvcmRlciBlbW90aW9ucy4gVGhlIG1vc3QgaW1wb3J0YW50IGltcGFjdCBpcyB0aGUgZGljaG90b215IGJldHdlZW4gcG9zaXRpdmUgJiBuZWdhdGl2ZSBlbW90aW9ucy4gCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9Cm5yYyA8LSBnZXRfc2VudGltZW50cygibnJjIikKbnJjIDwtIG5yYyAlPiUKICBtdXRhdGUoc2VudGltZW50ID0gYXMuZmFjdG9yKHNlbnRpbWVudCksCiAgICAgICAgIHNlbnRpbWVudCA9IHJlY29kZV9mYWN0b3Ioc2VudGltZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGpveSA9ICJqb3kiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydXN0ID0gInRydXN0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJwcmlzZSA9ICJzdXJwcmlzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW50aWNpcGF0aW9uID0gImFudGljaXBhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAicG9zaXRpdmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5lZ2F0aXZlID0gIm5lZ2F0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYWRuZXNzID0gInNhZG5lc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuZ2VyID0gImFuZ2VyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWFyID0gImZlYXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpZ3VzdCA9ICJkaXNndXN0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3JkZXJlZCA9IFQpKQpgYGAKCldlIHRoZW4gY3JlYXRlIHRoZSBtZXJnZWQgZGF0YXNldC4KCmBgYHtyfQplbW90aW9ucyA8LSB0b2tlbnNfdGltZSAlPiUgCiAgaW5uZXJfam9pbihucmMpICU+JSAgICAgICAgICAgICAjIE1lcmdlIGRhdGEgd2l0aCBzZW50aW1lbnQKICBtdXRhdGUoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpLAogICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KS8yNCwKICAgICAgICAgbWludXRlID0gbWludXRlKGNyZWF0ZWRfYXQpLzI0LzYwLAogICAgICAgICB0aW1lID0gZGF5K2hvdXIrbWludXRlKSAgICMgQ3JlYXRlIGRheSBjb2x1bW4KZW1vdGlvbnMgICAgICAgICAgICAgICAgICAgICAgICAgICMgU2hvdyB0aGUgcmVzdWx0CmBgYAoKVGhlIG1lcmdpbmcgaGFzIHJlZHVjZWQgdGhlIHNpemUgb2YgdGhlIGRhdGFzZXQsIGJ1dCB0aGVyZSBzdGlsbCByZW1haW5zIGVub3VnaCB0byBwdXJzdWUgdGhlIHN0dWR5LiAgIApGaW5hbGx5LCB3ZSBtb3ZlIHRvIHRoZSBwaXZvdC10YWJsZSB0aGF0IGNvdW50cyBlbW90aW9ucyBmb3IgZWFjaCBkYXkuCgpgYGB7cn0KZyA8LSBlbW90aW9ucyAlPiUgCiAgZ3JvdXBfYnkodGltZSwgc2VudGltZW50LCBkYXksIGhvdXIsIG1pbnV0ZSkgJT4lCiAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lCiAgbXV0YXRlKHRpbWUgPSBtYWtlX2RhdGV0aW1lKHllYXIgPSAyMDIwLCBtb250aCA9IDEwLCBkYXkgPSBkYXksIGhvdXIgPSBob3VyKjI0LCBtaW4gPSBtaW51dGUqMjQqNjApKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gaW50ZW5zaXR5LCBmaWxsID0gc2VudGltZW50KSkgKyBnZW9tX2NvbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpCmdncGxvdGx5KGcpCmBgYAoKVGhpcyBjYW4gYWxzbyBiZSBzaG93biBpbiBwZXJjZW50YWdlIGZvcm1hdC4gCgpgYGB7cn0KZyA8LSBlbW90aW9ucyAlPiUgCiAgZ3JvdXBfYnkodGltZSwgc2VudGltZW50LCBkYXksIGhvdXIsIG1pbnV0ZSkgJT4lCiAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lCiAgbXV0YXRlKHRpbWUgPSBtYWtlX2RhdGV0aW1lKHllYXIgPSAyMDIwLCBtb250aCA9IDEwLCBkYXkgPSBkYXksIGhvdXIgPSBob3VyKjI0LCBtaW4gPSBtaW51dGUqMjQqNjApKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gaW50ZW5zaXR5LCBmaWxsID0gc2VudGltZW50KSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJmaWxsIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gODAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArIHhsYWIoIlRpbWUiKQpnZ3Bsb3RseShnKQpgYGAKCkdvaW5nIGZ1cnRoZXIgd291bGQgcHJvYmFibHkgaW52b2x2ZSAqbi1ncmFtcyosIHNlZSBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vbmdyYW1zLmh0bWwKCgojIyBBZHZhbmNlZCBzZW50aW1lbnQgCgpUaGUgcHJvYmxlbSB3aXRoIHRoZSBwcmVjZWRpbmcgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZG9uJ3QgdGFrZSBpbnRvIGFjY291bnQgKip2YWxlbmNlIHNoaWZ0ZXJzKiogKGkuZS4sIG5lZ2F0b3JzLCBhbXBsaWZpZXJzIChpbnRlbnNpZmllcnMpLCBkZS1hbXBsaWZpZXJzIChkb3dudG9uZXJzKSwgYW5kIGFkdmVyc2F0aXZlIGNvbmp1bmN0aW9ucykuIElmIGEgdHdlZXQgc2F5cyAqbm90IGhhcHB5KiwgY291bnRpbmcgdGhlIHdvcmQgKmhhcHB5KiBpcyBub3QgYSBnb29kIGlkZWEhIFRoZSBwYWNrYWdlICpzZW50aW1lbnRyKiBpcyBidWlsdCB0byBjaXJjdW12ZW50IHRoZXNlIGlzc3VlczogaGF2ZSBhIGxvb2sgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvc2VudGltZW50ciAgCihzZWUgYWxzbzogaHR0cHM6Ly93d3cuc2VudG9tZXRyaWNzLm9yZykKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2VudGltZW50cikpe2luc3RhbGwucGFja2FnZXMoYygic2VudGltZW50ciIsICJ0ZXh0Y2F0IikpfQpsaWJyYXJ5KHNlbnRpbWVudHIpCmxpYnJhcnkodGV4dGNhdCkKYGBgCgpGaXJzdCwgbGV0J3Mga2VlcCBvbmx5IHRoZSB0d2VldHMgd3JpdHRlbiBpbiBFbmdsaXNoIQoKYGBge3J9CnR3ZWV0c19lbiA8LSB0d2VldHMgJT4lCiAgbXV0YXRlKGxhbmd1YWdlID0gdGV4dGNhdCh0ZXh0KSkgJT4lCiAgZmlsdGVyKGxhbmd1YWdlID09ICJlbmdsaXNoIikgJT4lCiAgZHBseXI6OnNlbGVjdChjcmVhdGVkX2F0LCB0ZXh0KQpgYGAKCioqTk9URSoqOiB0aGUgY29kZSBhYm92ZSB3YXMgdXNlZCB0byBzaG93IHRoZSBmdW5jdGlvbiAqdGV4dGNhdCo6IHRoZSBsYW5ndWFnZSBpcyBhbHJlYWR5IGNvZGVkIGluIHRoZSB0d2VldHMgdmlhIHRoZSAqKmxhbmcqKiBjb2x1bW4vdmFyaWFibGUuIChpdCBzdWZmaWNlcyB0byBrZWVwIHRoZSBpbnN0YW5jZXMgZm9yIHdoaWNoIGxhbmcgPT0gImVuIikKCk5leHQsIHdlIGNvbXB1dGUgYWR2YW5jZWQgc2VudGltZW50LiAKCmBgYHtyfQp0d2VldF9zZW50IDwtIHR3ZWV0c19lbiR0ZXh0ICU+JQogIGdldF9zZW50ZW5jZXMoKSAlPiUgICMgSW50ZXJtZWRpYXRlIGZ1bmN0aW9uCiAgc2VudGltZW50KCkgICAgICAgICAgIyBTZW50aW1lbnQhCnR3ZWV0X3NlbnQKYGBgCgoqKk5PVEUqKjogZGVwZW5kaW5nIG9uIGZyZXF1ZW5jeSBpc3N1ZXMsIGl0IGlzIGJldHRlciB0byBhbmFseXplIGF0IGRhaWx5IG9yIGhvdXJseSBzY2FsZXMuIElmIGEgd29yZCBpcyB2ZXJ5IHBvcHVsYXIsIHRoZW4sIGhpZ2hlciBmcmVxdWVuY2llcyBhcmUgbW9yZSByZWxldmFudC4gCgpgYGB7cn0KdHdlZXRzX2VuICU+JQogIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICMgVGhpcyBjcmVhdGVzIGEgbmV3IGNvbHVtbiB3aXRoIHJvdyBudW1iZXIKCnR3ZWV0c19lbiAlPiUKICByb3dpZF90b19jb2x1bW4oImVsZW1lbnRfaWQiKSAlPiUKICBsZWZ0X2pvaW4odHdlZXRfc2VudCwgYnkgPSAiZWxlbWVudF9pZCIpCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICBncm91cF9ieShkYXkgPSBkYXkoY3JlYXRlZF9hdCkpICU+JQogIHN1bW1hcmlzZShhdmdfc2VudCA9IG1lYW4oc2VudGltZW50KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGRheSksIHkgPSBhdmdfc2VudCkpICsgZ2VvbV9jb2woKSArIHhsYWIoImRheSIpCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoZGF5KGNyZWF0ZWRfYXQpKSwgeSA9IHNlbnRpbWVudCkpICsgCiAgZ2VvbV9qaXR0ZXIoc2l6ZSA9IDAuMikgKwogIGdlb21fYm94cGxvdChhZXMoY29sb3IgPSBhcy5mYWN0b3IoZGF5KGNyZWF0ZWRfYXQpKSksIGFscGhhID0gMC41KSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIHhsYWIoImRheSIpCmBgYAoKCgojIFJlc291cmNlcwoKQmVsb3csIGEgc2hvcnQgbGlzdCBvZiByZXNvdXJjZXMgKHRvIGFjY2VzcyB0aGlyZC1wYXJ0eSBkYXRhKTogICAKCi0gKip0ZXh0IG1pbmluZyB3aXRoIFIqKiAob25saW5lIGJvb2spOiBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20gICAgICAKLSAqKkJsb29tYmVyZyoqOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvUmJscGFwaS9pbmRleC5odG1sICAgCi0gKipnbWFpbCoqOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ21haWxyL3ZpZ25ldHRlcy9nbWFpbHIuaHRtbCAgIAotICoqR29vZ2xlIE1hcHMqKjogaHR0cHM6Ly9jcmFuLnJzdHVkaW8uY29tL3dlYi9wYWNrYWdlcy9tYXBzYXBpL3ZpZ25ldHRlcy9pbnRyby5odG1sICAKLSAqKkdvb2dsZSB0cmVuZHMqKjogaHR0cHM6Ly9naXRodWIuY29tL1BNYXNzaWNvdHRlL2d0cmVuZHNSCi0gKipHb29nbGUgQVBJcyoqIChtb3JlIGdlbmVyYWxseSk6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nYXJnbGUvdmlnbmV0dGVzL2F1dGgtZnJvbS13ZWIuaHRtbAoKClBvc3NpYmx5IGRlcHJlY2F0ZWQ6ICAKLSAqKkZhY2Vib29rKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9SZmFjZWJvb2svaW5kZXguaHRtbCAgICAKLSAqKkluc3RhZ3JhbSoqOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvaW5zdGFSL2luZGV4Lmh0bWwKCmBgYHtyfQoKYGBgCg==